package com.example.mybatisplusservice.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.mybatisplusservice.User;
import com.example.mybatisplusservice.mapper.UserMapper;
import com.example.mybatisplusservice.service.UserService;
import org.springframework.aop.framework.AopContext;
import org.springframework.aop.framework.AopProxyUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.context.annotation.Lazy;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;

/**
 * @author wuxinxin
 */
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper,User> implements UserService{

    @Autowired
    private UserMapper userMapper;

    @Autowired
    ApplicationContext applicationContext;

    @Async
    @Override
    public List<User> getOne(Integer userId) {
        return userMapper.selectByUserId(userId);
    }

    @Override
    public IPage<User> list(Integer page, Integer pageSize) {
        IPage<User> pageParam = new Page(1,2);


        IPage<User> userIPage = userMapper.selectPage(pageParam,
                new LambdaQueryWrapper<User>().eq(User::getUserId,1));


        System.out.println(userIPage);

        return userIPage;
    }

    /**
     * 使用mybatis plus自带分页插件
     * @param page
     * @param pageSize
     * @return
     */
    @Override
    public IPage<User> list2(Integer page, Integer pageSize,Integer childrenId) {
        IPage<User> pageParam = new Page(1,2);
        //一对多关掉countSql,否则会被错误优化掉left join
        //((Page)pageParam).setOptimizeCountSql(false);

        IPage<User> userIPage = userMapper.selectByUserIdDetail(pageParam, childrenId);


        System.out.println(userIPage);

        return userIPage;
    }

    /**
     * 测试 事务各种情况
     *
     * 1.只要有方法标注了@Transactional 那么就会生成代理对象，执行业务代码前都会走Interceptor
     *
     * 2.具体是不是会走一些advice,取决于具体方法，这个是和方法相关的（比如一些标注了@Transactional才会走事务拦截器，
     * 否则只是过一下Interceptor,后面还是直接执行业务方法，就不走增强逻辑）
     *
     * 3.动态代理都是这样的，创建代理对象时候，将所有可能的advice都会提前绑定，然后创建代理对象，具体执行，还是看方法上
     *   需要的增强是什么，这个是动态的拦截chain,还是比较灵活的
     *
     * 4. 举例：我一个类的A方法标注了@Transactional B方法标注了@Async,那么创建这个代理对象就会把两个增强都绑定到动态创建的
     *    代理对象上， 如果我执行A方法，那么只会执行事务的advice,如果我执行B方法，那么只会执行异步的advice
     *
     * 5.上面就是spring aop的原理，cglib 和 jdkDynamic都准守
     * @throws InterruptedException
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void addUser() throws InterruptedException {

        System.out.println("addUser"+Thread.currentThread().getName());

        QueryWrapper<User> select = new QueryWrapper<User>().select("max(id) id");

        User maxUser = userMapper.selectOne(select);

        maxUser = userMapper.selectById(maxUser.getId());

        System.out.println("最大user"+maxUser);

        //添加第一条记录
        /*User temp=new User();
        temp.setId(maxUser.getId()+1);
        temp.setUserId(maxUser.getUserId()+1);
        temp.setChildrenId(10);

        userMapper.insert(temp);*/

        //测试事务异步调用1
        /*applicationContext.getBean(UserService.class).addUserSetp(maxUser.getId()+1,
                maxUser.getUserId()+1,10);*/

        //测试事务同步调用1
        this.addUserSetp(maxUser.getId()+1,
                maxUser.getUserId()+1,10,false);



        //模拟报错
        //applicationContext.getBean(UserService.class).error();

        //模拟报错2
        //CompletableFuture completableFuture = applicationContext.getBean(UserService.class).errorOk();

        /*completableFuture.whenComplete((m,n)->{
            System.out.println("result:"+m+"====="+n);
        });*/

        //添加第二条记录
       /* User temp2=new User();
        temp2.setId(maxUser.getId()+2);
        temp2.setUserId(maxUser.getUserId()+1);
        temp2.setChildrenId(11);

        userMapper.insert(temp2);*/

       //测试事务异步调用2--异步调用，事务会失效

        //不使用applicationContext获取代理对象
        /*applicationContext.getBean(UserService.class).addUserSetp(maxUser.getId()+2,
                maxUser.getUserId()+1,11,false);*/

        //使用 AopContext.currentProxy()
        //@EnableAspectJAutoProxy(exposeProxy = true)设置exposeProxy=true
        UserService userService =(UserService)AopContext.currentProxy();
        userService.addUserSetp(maxUser.getId()+2,
                maxUser.getUserId()+1,11,false);

        //测试事务同步调用2
        /*this.addUserSetp(maxUser.getId()+2,
                maxUser.getUserId()+1,11);;*/

        int i=1/0;
    }

    @Override
    @Async
    public void error() throws InterruptedException {
        Thread.sleep(1000);
        System.out.println("error"+Thread.currentThread().getName());
        int i=10/0;
    }

    @Override
    @Async
    public CompletableFuture errorOk() throws InterruptedException {
        Thread.sleep(1000);
        System.out.println("error"+Thread.currentThread().getName());
        int i=10/0;
        return CompletableFuture.completedFuture(null);
    }

    /**
     *
     * 事务内部子方法开启异步对事务影响
     *
     * 1.开启了异步线程执行，那么和原来就不是一个sqlSession连接了，会导致事务失效
     *
     * 2.如果说子方法本来就想是一个独立事务，那么可以进行异步处理
     */
    //@Transactional(rollbackFor = Exception.class,propagation= Propagation.REQUIRES_NEW)
    @Transactional(rollbackFor = Exception.class)
    //@Async
    @Override
    public void addUserSetp(Integer id,Integer userId, Integer childrenId,Boolean isRollback) {
        System.out.println("addUserSetp 执行线程"+Thread.currentThread().getName());
        User temp2=new User();
        temp2.setId(id);
        temp2.setUserId(userId);
        temp2.setChildrenId(childrenId);

        userMapper.insert(temp2);

        if(isRollback){
            int i=10/0;
        }
    }

    private User getMaxUeer(){
        QueryWrapper<User> select = new QueryWrapper<User>().select("max(id) id");

        User maxUser = userMapper.selectOne(select);

        maxUser = userMapper.selectById(maxUser.getId());

        System.out.println("最大user"+maxUser);

        return maxUser;
    }

    /**
     * 测试两个独立事务情况， 外部方法不加事务，看看两个事务是否独立
     *
     * 测试结果： 外部方法不加事务，内部子方法事务是独立提交和回滚的
     */
    @Override
    public void testTransactional1(){
        System.out.println("testTransactional1"+Thread.currentThread().getName());

        //获取最后一个user
        User maxUeer = getMaxUeer();

        UserService userService =(UserService)AopContext.currentProxy();

        //进行第一个事务
        userService.addUserSetp(maxUeer.getId()+1,
                maxUeer.getUserId()+1,10,false);

        //进行第二个事务
        userService.addUserSetp(maxUeer.getId()+2,
                maxUeer.getUserId()+1,11,true);
    }

    /**
     * 测试mybatis plus batch操作是否支持事务
     *
     * 测试结果： 支持事务,使用了事务注解（注意调用的时候，不要让事务失效--AopContext.currentProxy()）
     */
    @Override
    public void testTransactional2() {

        //获取最后一个user
        User maxUeer = getMaxUeer();

        User temp1=new User();
        temp1.setId(maxUeer.getId()+1);
        temp1.setUserId(maxUeer.getUserId()+1);
        temp1.setChildrenId(10);

        User temp2=new User();
        temp2.setId(maxUeer.getId()+1);
        temp2.setUserId(maxUeer.getUserId()+1);
        temp2.setChildrenId(11);

        ((UserService)AopContext.currentProxy()).saveBatch(Arrays.asList(temp1,temp2));

    }
}
